/**
 * 
 */
package com.module.hq.engine;

import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.io.InputStream;
import java.util.ArrayList;

import javax.swing.SwingUtilities;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import com.dalonedrau.graph.WeightedGraphEdge;
import com.dalonedrau.jogl.engine.Action;
import com.dalonedrau.jogl.engine.Animation;
import com.dalonedrau.jogl.engine.GameDrawable;
import com.dalonedrau.jogl.engine.GlobalConsts;
import com.dalonedrau.jogl.engine.Interactive;
import com.dalonedrau.jogl.engine.Interface;
import com.dalonedrau.jogl.engine.JOGLErrorHandler;
import com.dalonedrau.jogl.engine.ProjectConstants;
import com.dalonedrau.jogl.engine.Render;
import com.dalonedrau.jogl.engine.Sprite;
import com.dalonedrau.jogl.engine.Time;
import com.dalonedrau.jogl.opengl.animation.AnimationSequenceObjectFactory;
import com.dalonedrau.jogl.opengl.sprite.BasicSpriteObject;
import com.dalonedrau.jogl.opengl.sprite.SpriteBase;
import com.dalonedrau.jogl.opengl.sprite.SpriteImageObject;
import com.dalonedrau.jogl.opengl.sprite.SpriteImageObjectFactory;
import com.dalonedrau.jogl.opengl.ui.PanningMap;
import com.dalonedrau.jogl.opengl.ui.border.GuiBorder;
import com.dalonedrau.jogl.util.UtilityMethods;
import com.dalonedrau.pool.PooledStringBuilder;
import com.dalonedrau.pool.StringBuilderPool;
import com.dalonedrau.vectors.Vector2;
import com.dalonedrau.vectors.Vector3;
import com.dalonedrau.xmlparser.XMLParserUtility;
import com.module.hq.bus.flyweights.HQTerrainTypes;
import com.module.hq.bus.flyweights.UltimaTileCell;
import com.module.hq.bus.flyweights.UltimaTileMap;
import com.module.hq.bus.globals.HQGlobals;
import com.module.hq.graph.HQCellGraph;
import com.module.hq.ui.mono.dungeonview.widgets.HQActionMenu;
import com.module.hq.view.DungeonScreenView;
import com.rpg.base.TwoDMap;
import com.rpg.base.flyweight.ScriptVariable;
import com.rpg.base.globals.RPGGlobals;
import com.rpg.base.interactive.flyweight.BaseInteractiveObject;

/**
 * Class responsible for maintaining the state and display of the current 
 * dungeon map being played on.  Also handles the animation states for actions
 * taking place on the map.
 * @author Donald
 */
@SuppressWarnings("rawtypes")
public final class HQMap extends PanningMap
		implements TwoDMap<HQInteractiveObject> {
	/** travel animations. */
	public static final int		ANIM_01_IO_MOVING				= 1;
	/** no animations. */
	public static final int		ANIM_00_NONE					= 0;
	/** room opening animations. */
	public static final int		ANIM_02_ROOM_OPENING			= 2;
	/** secret door revealing animations. */
	public static final int		ANIM_03_SHOW_SECRET_DOORS			= 3;
	/**
	 * during the room opening animation, the room should be exposed after
	 * 450ms.
	 */
	public static final int		ANIMATION_ROOM_EXPOSE_AFTER	= 450;
	/** the one and only instance of the <code>HQMap</code> class. */
	private static HQMap		instance;
	/** the z-level at which IO highlights or effects are rendered. */
	public static final float	Z_LEVEL_IO_HIGHLIGHTS		= 4;
	/** the z-level at which IOs are rendered. */
	public static final float	Z_LEVEL_IOS					= 3;
	/** the z-level at which tiles highlights are rendered. */
	public static final float	Z_LEVEL_TILE_HIGHLIGHTS		= 2;
	/** the z-level at which map tiles are rendered. */
	public static final float	Z_LEVEL_TILES				= 1;
	/** the z-level at which widgets are rendered. */
	public static final float	Z_LEVEL_WIDGETS				= 5;
	/**
	 * Gives access to the singleton instance of {@link HQMap}.
	 * @return {@link HQMap}
	 */
	public static HQMap getInstance() {
		if (HQMap.instance == null) {
			HQMap.instance = new HQMap();
		}
		return HQMap.instance;
	}
	private long					animLastUpdate;
	private boolean					animPaused		= false;
	private long					animStart;
	/** the current animation. */
	private int						currAnimation	= HQMap.ANIM_00_NONE;
	/** the current map being played. */
	private UltimaTileMap			currentMap;
	boolean							doonce			= false;
	/** the cursor highlight. */
	private BaseInteractiveObject	highlight;
	/** the locations of all interactive objects on the map. */
	private int[]					ioLocs;
	/** all interactive objects in play. */
	private int[]					ios;
	private int						lastPathEnd		= -1;
	/** the number of interactive objects in play. */
	private int						numIosInPlay;
	/**
	 * flag indicating whether the ending pause has been played for a travel
	 * action.
	 */
	private boolean					playedEndTravelPause;
	/** the map's size. */
	private Dimension				size;
	/** the list of IO sprites. */
	private SpriteBase[]			spritesIOs;
	/** the list of movement path sprites. */
	private SpriteBase[]			spritesMovePath;
	/** the list of tile sprites. */
	private SpriteBase[]			spritesTiles;
	/** the list of possible travel locations this turn. */
	private int[]					travelLocs;
	/**
	 * Creates a new instance of {@link HQMap}.
	 */
	private HQMap() {
		ios = new int[0];
		ioLocs = new int[0];
		for (int i = 0; i < ios.length; i++) {
			ios[i] = -1;
		}
		spritesIOs = new SpriteBase[0];
		numIosInPlay = 0;
		int numTilesViewed =
				(HQGlobals.MAP_WIDTH + 1) * (HQGlobals.MAP_HEIGHT + 1);
		super.setViewportWidth(HQGlobals.MAP_WIDTH * getTileMapCellSize());
		super.setViewportHeight(HQGlobals.MAP_HEIGHT * getTileMapCellSize());
		spritesTiles = new SpriteBase[numTilesViewed];
		for (int i = 0; i < spritesTiles.length; i++) {
			spritesTiles[i] = new BasicSpriteObject();
		}
		try {
			spritesMovePath = new SpriteBase[GlobalConsts.INT_018];
			for (int i = 0; i < spritesMovePath.length; i++) {
				spritesMovePath[i] = new BasicSpriteObject();
				SpriteImageObject img =
						SpriteImageObjectFactory.getInstance().getImageByName(
								"move_overlay");
				spritesMovePath[i].setImageRefId(img.getRefId());
				spritesMovePath[i].setWidth(img.getWidth());
				spritesMovePath[i].setHeight(img.getHeight());
			}
			highlight =
					(BaseInteractiveObject) Interactive.getInstance()
							.getNewIO();
			SpriteBase sb = new BasicSpriteObject();
			SpriteImageObject img =
					SpriteImageObjectFactory.getInstance().getImageByName(
							"hq_ultima_inv_sel_cursor_0");
			sb.setImageRefId(img.getRefId());
			sb.setWidth(img.getWidth());
			sb.setHeight(img.getHeight());
			highlight.setSprite(sb);
			highlight.addAnimation("selection",
					AnimationSequenceObjectFactory.getInstance()
							.getSequenceRefId("hq_ultima_inv_sel_cursor"));
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addIOToMap(final float x, final float y,
			final HQInteractiveObject io) {
		if (io != null) {
			// check to see if io is already tracked
			boolean inList = false;
			int index;
			for (index = 0; index < ios.length; index++) {
				if (Interactive.getInstance().hasIO(ios[index])
						&& ios[index] == io.getRefId()) {
					inList = true;
					break;
				}
			}
			if (!inList) {
				// add io to map
				index = numIosInPlay++;
				if (index >= ios.length) {
					// copy ios array
					int[] dest = new int[ios.length + 1];
					System.arraycopy(ios, 0, dest, 0, ios.length);
					ios = dest;
					// copy ioLocs array
					dest = new int[ioLocs.length + 1];
					System.arraycopy(ioLocs, 0, dest, 0, ioLocs.length);
					ioLocs = dest;
					dest = null;
					// copy ioSprites array
					SpriteBase[] destsp = new SpriteBase[spritesIOs.length + 1];
					System.arraycopy(spritesIOs, 0, destsp, 0,
							spritesIOs.length);
					destsp[spritesIOs.length] = new BasicSpriteObject();
					spritesIOs = destsp;
					destsp = null;
				}
				ios[index] = io.getRefId();
				// add io to turn counter
				if (io.hasIOFlag(HQGlobals.IO_01_PC)
						|| io.hasIOFlag(HQGlobals.IO_03_NPC)) {
					HQTurn.getInstance().addIO(io.getRefId());
				}
			}
			// UPDATE IO'S LOCATION
			ioLocs[index] =
					UtilityMethods.getInstance().convertPointToInt(x, y);
			try {
				// set IO visited room
				int[] allrooms = currentMap.getRoomsForCell((int) x, (int) y);
				PooledStringBuilder sb =
						StringBuilderPool.getInstance().getStringBuilder();
				sb.append(HQScript.getInstance().getGlobalStringVariableValue(
						"currentMapName"));
				sb.append("_roomsVisited");
				ScriptVariable svar =
						io.getScript().getLocalVariable(sb.toString());
				if (svar == null) {
					svar = new ScriptVariable(sb.toString(),
							RPGGlobals.TYPE_L_11_INT_ARR, new int[0]);
				}
				// set new list of rooms visited
				int[] visited = svar.getIntArrayVal();
				for (int i = 0; i < allrooms.length; i++) {
					boolean found = false;
					for (int inner = 0; inner < visited.length; inner++) {
						if (allrooms[i] == visited[inner]) {
							found = true;
							break;
						}
					}
					if (!found) {
						// extend the visited array
						int[] dest = new int[visited.length + 1];
						System.arraycopy(visited, 0, dest, 0, visited.length);
						dest[visited.length] = allrooms[i];
						visited = dest;
						dest = null;
					}
				}
				svar.setIntArrayVal(visited);
				io.getScript().setLocalVariable(svar);
				sb.returnToPool();
			} catch (Exception ex) {
				JOGLErrorHandler.getInstance().fatalError(ex);
			}
		}
	}
	/**
	 * Centers the map on a specific cell.
	 * @param x the hex's x-position
	 * @param y the hex's y-position
	 */
	public void centerMapOnCell(final int x, final int y) {
		Vector2 pt = getCellCenterPoint(x, y);
		// center the viewport on the center point of the cell
		super.setViewX((int) pt.getX()
				- HQGlobals.MAP_WIDTH * getTileMapCellSize() / 2);
		super.setViewY((int) pt.getY()
				- HQGlobals.MAP_HEIGHT * getTileMapCellSize() / 2);
		if (super.getViewX() < 0) {
			super.setViewX(0);
		} else if (super.getViewX() > super.getMapWidth()
				- HQGlobals.MAP_WIDTH * getTileMapCellSize()) {
			super.setViewX(super.getMapWidth()
					- HQGlobals.MAP_WIDTH * getTileMapCellSize());
		}
		if (super.getViewY() < 0) {
			super.setViewY(0);
		} else if (super.getViewY() > super.getMapHeight()
				- HQGlobals.MAP_HEIGHT * getTileMapCellSize()) {
			super.setViewY(super.getMapHeight()
					- HQGlobals.MAP_HEIGHT * getTileMapCellSize());
		}
	}
	/**
	 * Centers the map on a specific hex.
	 * @param v the hex's location
	 */
	public void centerMapOnCell(final Vector2 v) {
		centerMapOnCell((int) v.getX(), (int) v.getY());
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void centerMapOnIO(final int refid) {
		int index = getIOIndex(refid);
		if (index > -1) {
			// found the IO. get the IO's location
			// convert location to Point
			Point pt =
					UtilityMethods.getInstance().convertIntToPoint(
							ioLocs[index]);
			centerMapOnCell(pt.x, pt.y);
			pt = null;
		}
	}
	/**
	 * Centers the map on a specific room.
	 * @param roomId the room's id
	 */
	public void centerMapOnRoom(final int roomId) {
		Vector2 v = getRoomCenterPoint(roomId);
		super.setViewX((int) v.getX() - HQGlobals.MAP_WIDTH
				* getTileMapCellSize() / 2);
		super.setViewY((int) v.getY() - HQGlobals.MAP_HEIGHT
				* getTileMapCellSize() / 2);
		if (super.getViewX() < 0) {
			super.setViewX(0);
		} else if (super.getViewX() > super.getMapWidth()
				- HQGlobals.MAP_WIDTH * getTileMapCellSize()) {
			super.setViewX(super.getMapWidth()
					- HQGlobals.MAP_WIDTH * getTileMapCellSize());
		}
		if (super.getViewY() < 0) {
			super.setViewY(0);
		} else if (super.getViewY() > super.getMapHeight()
				- HQGlobals.MAP_HEIGHT * getTileMapCellSize()) {
			super.setViewY(super.getMapHeight()
					- HQGlobals.MAP_HEIGHT * getTileMapCellSize());
		}
	}
	/** Clears the highlight cursor. */
	public void clearHighlight() {
		if (highlight != null) {
			removeFlag(GlobalConsts.MAP_FLAG_PARTY_HOVER);
			Animation.getInstance().stopAllAnimations(highlight);
			highlight.getSprite().setPosition(null);
		}
	}
	/** Clears the movement path. */
	private void clearMovementPath() {
		for (int i = 0; i < spritesMovePath.length; i++) {
			spritesMovePath[i].setPosition(null);
		}
	}
	/**
	 * Handles actions when the user clicks a cell on the map.
	 * @param moc the integer representing the cell the user is hovering
	 */
	private void clickCell(final int moc) {
		try {
			if (super.hasFlag(
					GlobalConsts.MAP_FLAG_ALLOW_ACTION_MENU)) {
				// see if the mouse is over a cell you can travel to
				lastPathEnd = -1;
				boolean doTravel = false;
				for (int i = 0; i < travelLocs.length; i++) {
					if (travelLocs[i] == moc) {
						doTravel = true;
						break;
					}
				}
				if (doTravel) {
					// send event to tell IO it is now
					// traveling to this location
					int ioid =
							HQScript.getInstance().getGlobalIntVariableValue(
									"ioTurn");
					int index = getIOIndex(ioid);
					if (index == -1) {
						PooledStringBuilder sb =
								StringBuilderPool.getInstance()
										.getStringBuilder();
						sb.append("Cannot start travel for io ");
						sb.append(index);
						Exception ex = new Exception(sb.toString());
						sb.returnToPool();
						throw ex;
					}
					HQInteractiveObject io =
							(HQInteractiveObject) Interactive.getInstance()
									.getIO(
											ioid);
					// set the io's current destination to next travel point
					// test to see if there is a path to that node
					io.getScript().setLocalVariable(
							"ioFinalDest", new Integer(moc));
					int nextNode =
							HQMovement.getInstance().getNextTileInPath(ioid);
					if (nextNode == -1) {
						PooledStringBuilder sb =
								StringBuilderPool.getInstance()
										.getStringBuilder();
						sb.append("No valid path for IO at ");
						sb.append(
								UtilityMethods.getInstance().convertIntToPoint(
										ioLocs[index]));
						sb.append(" to travel to ");
						sb.append(
								UtilityMethods.getInstance().convertIntToPoint(
										moc));
						sb.append(".");
						Exception ex = new Exception(sb.toString());
						sb.returnToPool();
						throw ex;
					}
					long time = Time.getInstance().getFrameStart();
					HQScript.getInstance().sendIOScriptEvent(
							io, // io
							HQGlobals.SM_21_MOVE, // message
							new Object[] {
									"ioFinalDest", new Integer(moc),
									"ioCurrentDest", new Integer(nextNode),
									"moveStart", new Long(time),
									"moveLastUpdate", new Long(time),
									"movePaused", new Integer(0),
									"playedEndTravelPause", new Integer(0)
									},
							"");
				} else {
					// player click a door?
					int ioid =
							HQScript.getInstance().getGlobalIntVariableValue(
									"ioTurn");
					if (currentMap.isCellDoor(moc)
							&& currentMap.cellsAdjacent(
									moc, ioLocs[getIOIndex(ioid)], false)) {
						HQMovement.getInstance().openDoor(moc);
					}
				}
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * Handles actions when the user is interacting with an IO.
	 * @param ioid the IO's reference id
	 */
	private void clickIO(final int ioid) {
		try {
			clearHighlight();
			// an IO was clicked
			HQInteractiveObject io =
					(HQInteractiveObject) Interactive.getInstance().getIO(
							ioid);
			if (io.hasIOFlag(HQGlobals.IO_01_PC)) {
				if (io.getRefId() == HQController.getInstance().getPcId()) {
					// THE PLAYER WAS CLICKED
					if (super.hasFlag(
							GlobalConsts.MAP_FLAG_ALLOW_ACTION_MENU)) {
						// PARTY WAS CLICKED
					}
				} else {
					// A NON-PLAYER HERO WAS CLICKED
				}
			} else if (io.hasIOFlag(HQGlobals.IO_03_NPC)) {
				// AN NPC WAS CLICKED
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * Creates a cell to be added to a map created while loading a file.
	 * @param element the xml element being read
	 * @return UltimaTileCell
	 */
	private UltimaTileCell createCell(final Element element) {
		int x = XMLParserUtility.getInstance().getIntValue(element, "x");
		int y = XMLParserUtility.getInstance().getIntValue(element, "y");
		UltimaTileCell cell = null;
		try {
			String type = XMLParserUtility.getInstance().getTextFromElement(
					element, "type");
			if (type != null && !type.equalsIgnoreCase("null")) {
				HQTerrainTypes.getInstance().addTerrain(type);
				int terrainId = HQTerrainTypes.getInstance().getTerrainId(type);
				int flags = XMLParserUtility.getInstance().getIntValue(
						element, "flags");
				cell = new UltimaTileCell(x, y, terrainId, flags);
			}
		} catch (Exception iae) {
			iae.printStackTrace();
		}
		return cell;
	}
	private void displayActionMenu() {

	}
	/**
	 * Gets the reference id of any IO PCs or NPCs at a specific location. If no
	 * IO is found, -1 is returned.
	 * @param x the x-ccordinate
	 * @param y the y-ccordinate
	 * @return <code>int</code>
	 */
	public int getActiveIOsAtLocation(final int x, final int y) {
		int id = -1;
		try {
			for (int i = 0; i < ioLocs.length; i++) {
				if (ioLocs[i] == UtilityMethods.getInstance()
						.convertPointToInt(x, y)) {
					// found IO at location
					// is it PC or NPC?
					HQInteractiveObject io =
							(HQInteractiveObject) Interactive.getInstance()
									.getIO(ios[i]);
					if (io.hasIOFlag(HQGlobals.IO_01_PC)
							|| io.hasIOFlag(HQGlobals.IO_03_NPC)) {
						id = i;
						break;
					}
				}
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
		return id;
	}
	/**
	 * Gets the list of IOs by reference id in a room.
	 * @param room the room id
	 * @return <code>int</code>[]
	 */
	public int[] getAllIOsForRoom(final int room) {
		int[] list = new int[0];
		// check all ios' room locations
		for (int i = 0; i < ioLocs.length; i++) {
			Point pt = UtilityMethods.getInstance().convertIntToPoint(
					ioLocs[i]);
			int x = pt.x, y = pt.y;
			pt = null;
			// get each room the io currently occupies
			int[] rooms = currentMap.getRoomsForCell(x, y);
			boolean add = false;
			for (int j = 0; j < rooms.length; j++) {
				if (rooms[j] == room) {
					// the io is in the room
					add = true;
					break;
				}
			}
			if (add) {
				// add the io's ref id to the list
				int[] dest = new int[list.length + 1];
				System.arraycopy(list, 0, dest, 0, list.length);
				dest[list.length] = ios[i];
				list = dest;
				dest = null;
			}
		}
		return list;
	}
	/**
	 * Gets the center point for a specific cell.
	 * @param cellInt the integer representing the cell's map coordinates
	 * @return {@link Vector2}
	 */
	public Vector2 getCellCenterPoint(final int cellInt) {
		return this.getCellCenterPoint(
				UtilityMethods.getInstance().convertIntToPoint(cellInt).x,
				UtilityMethods.getInstance().convertIntToPoint(cellInt).y);
	}
	/**
	 * Gets the center point for a specific cell.
	 * @param col the cell column being looked up
	 * @param row the cell row being looked up
	 * @return {@link Vector2}
	 */
	public Vector2 getCellCenterPoint(final int col, final int row) {
		// cell 0,0 starts at 0,0
		double x = getTileMapCellSize() * col;
		double y = getTileMapCellSize() * row;
		x += getTileMapCellSize() / 2;
		y += getTileMapCellSize() / 2;
		return new Vector2(x, y);
	}
	/**
	 * Gets the center point of a cell's on-screen position.
	 * @param col the cell column being looked up
	 * @param row the cell row being looked up
	 * @return {@link Vector2}
	 */
	public Vector2 getCellCenterPosition(final int col, final int row) {
		Vector3 v = getCellPosition(col, row, 0);
		// cell 0,0 starts at 0,0
		double x = v.getX();
		double y = v.getY();
		v = null;
		x += getTileMapCellSize() / 2;
		y += getTileMapCellSize() / 2;
		return new Vector2(x, y);
	}
	private int getCellMouseIsOver(final float x, final float y) {
		Vector3 v = getCellPosition(0, 0, 1);
		int cx = (int) (x - v.getX()), cy = (int) (y - v.getY());
		cx /= getTileMapCellSize();
		cy /= getTileMapCellSize();
		return UtilityMethods.getInstance().convertPointToInt(cx, cy);
	}
	/**
	 * Gets the screen position of a specific tile cell.
	 * @param index the inventory slot
	 * @param zMod any modification the the z-axis applied
	 * @return {@link Vector3}
	 */
	public Vector3 getCellPosition(final int cellX, final int cellY,
			final float zMod) {
		// x starts out at the left-side of the widget
		float x = (float) super.getPosition().getX();
		if (super.getBorder() != null) {
			x += super.getBorder().getInsets().left;
		}
		float y = (float) super.getPosition().getY();
		if (super.getBorder() != null) {
			y += super.getBorder().getInsets().bottom;
		}
		float z = (float) super.getPosition().getZ();
		z += (float) GlobalConsts.CHILD_STEP * zMod;

		// add cell's column position
		x += cellX * getTileMapCellSize();
		// add cell's row position
		y += cellY * getTileMapCellSize();
		// adjust for viewport offset
		x -= super.getViewX();
		y -= super.getViewY();
		return new Vector3(x, y, z);
	}
	/**
	 * Gets the type for a specific cell.
	 * @param x the cell's x-coordinate
	 * @param y the cell's y-coordinate
	 * @return <code>int</code>
	 */
	public int getCellType(final int x, final int y) {
		int type = 0;
		try {
			type = currentMap.getCell(x, y).getType();
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
		return type;
	}
	/**
	 * Gets the graph node index for a specific interactive object.
	 * @param ioid the IO's reference id
	 * @return <code>int</code>
	 * @throws Exception if the IO doesn't exist, or isn't on the map
	 */
	public int getGraphNodeIndexForIO(final int ioid) throws Exception {
		if (!Interactive.getInstance().hasIO(ioid)) {
			PooledStringBuilder sb =
					StringBuilderPool.getInstance().getStringBuilder();
			sb.append("IO ref id ");
			sb.append(ioid);
			sb.append(" does not exist.");
			Exception ex = new Exception(sb.toString());
			sb.returnToPool();
			throw ex;
		}
		if (Interactive.getInstance().getIO(ioid) == null) {
			PooledStringBuilder sb =
					StringBuilderPool.getInstance().getStringBuilder();
			sb.append("IO ref id ");
			sb.append(ioid);
			sb.append(" does not exist.");
			Exception ex = new Exception(sb.toString());
			sb.returnToPool();
			throw ex;
		}
		int index = -1;
		// find io's index in the list of ios
		for (int i = 0; i < ios.length; i++) {
			if (ios[i] == ioid) {
				index = i;
				break;
			}
		}
		if (index == -1) {
			throw new Exception("IO " + ioid + " is not on the map.");
		}// find the ios node
		Point pt = UtilityMethods.getInstance().convertIntToPoint(
				ioLocs[index]);
		int x = pt.x, y = pt.y;
		pt = null;
		return HQCellGraph.getInstance().getNode(x, y).getIndex();
	}
	/**
	 * Gets the list of IOs by reference id in a room.
	 * @param room the room id
	 * @return <code>int</code>[]
	 * @throws Exception if an error occurs
	 */
	public int[] getHostileIOsForRoom(final int room) throws Exception {
		int[] list = new int[0];
		// check all ios' room locations
		for (int i = 0; i < ioLocs.length; i++) {
			Point pt = UtilityMethods.getInstance().convertIntToPoint(
					ioLocs[i]);
			int x = pt.x, y = pt.y;
			pt = null;
			// get each room the io currently occupies
			int[] rooms = currentMap.getRoomsForCell(x, y);
			boolean add = false;
			for (int j = 0; j < rooms.length; j++) {
				if (rooms[j] == room) {
					// the io is in the room
					// is the IO hostile?
					HQInteractiveObject io =
							(HQInteractiveObject) Interactive.getInstance()
									.getIO(ios[i]);
					if (io.hasIOFlag(HQGlobals.IO_03_NPC)
							&& HQScript.getInstance().hasLocalVariable(
									ios[i], "enemy")
							&& HQScript.getInstance()
									.getLocalLongVariableValue(
											ios[i], "enemy") == 1) {
						add = true;
						break;
					}
				}
			}
			if (add) {
				// add the io's ref id to the list
				int[] dest = new int[list.length + 1];
				System.arraycopy(list, 0, dest, 0, list.length);
				dest[list.length] = ios[i];
				list = dest;
				dest = null;
			}
		}
		return list;
	}
	/**
	 * Gets the center point of a specific IO on the map.
	 * @param refid the IO's reference id
	 * @return {@link Vector2}
	 */
	private Vector2 getIOCenterPoint(final int refid) {
		Vector2 v = null;
		int index = getIOIndex(refid);
		if (index > -1) {
			v = getCellCenterPoint(ioLocs[index]);
		}
		return v;
	}
	/**
	 * Gets the index of an IO associated with the map. If the IO is not found,
	 * -1 is returned.
	 * @param ioid the IO's reference id
	 * @return <code>int</code>
	 */
	private int getIOIndex(final int ioid) {
		int index = -1;
		for (int i = 0; i < ios.length; i++) {
			if (ios[i] == ioid) {
				// found our io
				index = i;
				break;
			}
		}
		return index;
	}
	/**
	 * Gets the integer representation of an IO's map location.
	 * @param ioid the IO's reference id
	 * @return <code>int</code>
	 * @throws Exception if an error occurs
	 */
	public int getIOMapLocation(final int ioid) throws Exception {
		int index = getIOIndex(ioid);
		if (index == -1) {
			throw new Exception("IO is not on this map - " + index);
		}
		return ioLocs[index];
	}
	public int getNumIOsInPlay() {
		return numIosInPlay;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public Dimension getPreferredSize() {
		// size will be the size of the border, plus the size of the view
		if (size == null) {
			int w = super.getBorder().getInsets().left;
			w += super.getBorder().getInsets().right;
			int h = super.getBorder().getInsets().top;
			h += super.getBorder().getInsets().bottom;
			w += HQGlobals.MAP_WIDTH * getTileMapCellSize();
			h += HQGlobals.MAP_HEIGHT * getTileMapCellSize();
			size = new Dimension(w, h);
			if (getBorder() != null) {
				if (!(getBorder().getPreferredSize() != null
						&& getBorder().getPreferredSize().width
						== size.width && getBorder().getPreferredSize().height == size.height)) {
					getBorder().setPreferredSize(size.width,
							size.height);
				}
			}
		}
		return size;
	}
	/**
	 * Gets all cells associated with a specific room's id.
	 * @param roomId the room's reference id
	 * @return {@link UltimaTileCell}[]
	 */
	public UltimaTileCell[] getRoom(final int roomId) {
		return currentMap.getRoom(roomId);
	}
	/**
	 * Gets the center point for a specific room.
	 * @param room the room's id
	 * @return {@link Vector2}
	 */
	public Vector2 getRoomCenterPoint(final int room) {
		// get all cells for the room
		UltimaTileCell[] cells = currentMap.getRoom(room);
		// find the min,max x,y
		int minx = 999, maxx = 0, miny = 999, maxy = 0;
		for (int i = 0; i < cells.length; i++) {
			minx = Math.min(minx, cells[i].getX());
			maxx = Math.max(maxx, cells[i].getX());
			miny = Math.min(miny, cells[i].getY());
			maxy = Math.max(maxy, cells[i].getY());
		}
		cells = null;
		// find coordinates for the room
		// cell 0,0 starts at 0,0
		double minX = getTileMapCellSize() * minx;
		double maxX = getTileMapCellSize() * (maxx + 1);
		double minY = getTileMapCellSize() * miny;
		double maxY = getTileMapCellSize() * (maxy + 1);
		double x = minX + (maxX - minX) / 2;
		double y = minY + (maxY - minY) / 2;
		return new Vector2(x, y);
	}
	/**
	 * Gets the reference ids of all rooms associated with a specific cell
	 * index.
	 * @param cellIndex the cell's index
	 * @return <code>int</code>
	 * @throws Exception if an error occurs
	 */
	public int[] getRoomsForCell(final int cellIndex) throws Exception {
		return currentMap.getRoomsForCell(cellIndex);
	}
	/**
	 * Gets the room numbers that a specific IO is inhabiting. An IO can inhabit
	 * two or more rooms when it is standing in a tile cell such as a door that
	 * is in two or more rooms.
	 * @param id the IO's id
	 * @return <code>int</code>[]
	 * @throws Exception if an error occurs
	 */
	public int[] getRoomsForIO(final int id) throws Exception {
		int index = -1;
		// find io's index in the list of ios
		for (int i = 0; i < ios.length; i++) {
			if (ios[i] == id) {
				index = i;
				break;
			}
		}
		if (index == -1) {
			throw new Exception("IO " + id + " is not on the map.");
		}
		// find the ios rooms
		Point pt = UtilityMethods.getInstance().convertIntToPoint(
				ioLocs[index]);
		int x = pt.x, y = pt.y;
		pt = null;
		return currentMap.getRoomsForCell(x, y);
	}
	public int getTileMapCellSize() {
		return GlobalConsts.INT_032;
	}
	/**
	 * Gets the x-coordinate of the screen position of the viewport.
	 * @return <code>float</code>
	 */
	private float getViewPortX() {
		float viewPortX = (float) super.getPosition().getX();
		if (super.getBorder() != null) {
			viewPortX += super.getBorder().getInsets().left;
		}
		return viewPortX;
	}
	/**
	 * Gets the y-coordinate of the screen position of the viewport.
	 * @return <code>float</code>
	 */
	private float getViewPortY() {
		float viewPortY = (float) super.getPosition().getY();
		if (super.getBorder() != null) {
			viewPortY += super.getBorder().getInsets().bottom;
		}
		return viewPortY;
	}
	/** Handles any animation processes. */
	private void handleAnimation() {
		switch (currAnimation) {
		case ANIM_02_ROOM_OPENING:
			HQMovement.getInstance().playAnimationRoomOpening();
			break;
		case ANIM_03_SHOW_SECRET_DOORS:
			HQMovement.getInstance().playAnimationRevealSecretDoors();
			break;
		case ANIM_01_IO_MOVING:
		case ANIM_00_NONE:
			// do nothing
			break;
		default:
			JOGLErrorHandler.getInstance().fatalError(
					new Exception("Invalid animation state "
							+ currAnimation));
			break;
		}
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void handleMCActionMenuMode(MouseEvent e, float x, float y) {
		// TODO Auto-generated method stub

	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void handleMCFreeMode(final MouseEvent e, final float x,
			final float y) throws Exception {
		// System.out.println("handleMCFreeMode");
		// was this a right-click?
		if (SwingUtilities.isRightMouseButton(e)) {
			// RIGHT-CLICK
		} else {
			// was an IO clicked?
			int ioid = isMouseOverIO(x, y);
			if (ioid > -1) {
				clickIO(ioid);
			} else {
				clickCell(getCellMouseIsOver(x, y));
			}
		}
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void handleMCMoveSelectionMode(MouseEvent e, float x, float y) {
		// TODO Auto-generated method stub

	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void handleMOActionMenuMode(float x, float y) {
		// TODO Auto-generated method stub

	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void handleMOFreeMode(final float x, final float y)
			throws Exception {
		// System.out.println("handleMOFreeMode(" + x + "," + y);
		// how far am i from an edge?
		// define inner rectangle.
		boolean panPossible = false;
		if (isMouseInEdge(x, y)) {
			panPossible = checkForPanning(x, y);
		}
		if (panPossible) {
			// clear any PC highlights
			clearHighlight();
			// PANNING IS POSSIBLE - HANDLE MAP PANNING
			if (hasPanningTimerStarted()) {
				// check to see if it's time
				// to pan the map
				updatePanningTimer(Time.getInstance().getGameTime());
			} else { // start the panning timer
				startPanningTimer(Time.getInstance().getGameTime());
			}
		} else {
			// not panning
			setMoveHighlights();
			int moc = getCellMouseIsOver(x, y);
			// PANNING IS NOT POSSIBLE - STOP PANNING
			GameDrawable.setCursor(Cursor.DEFAULT_CURSOR);
			super.setPanning(GlobalConsts.DIR_NONE);
			// CHECK TO SEE IF MOUSE IS HOVERING PARTY SPRITE
			// if hovering is allowed
			if (super.hasFlag(GlobalConsts.MAP_FLAG_ALLOW_ACTION_MENU)) {
				// action menu is allowed. check to see what we're hovering
				int ioid = isMouseOverIO(x, y);
				if (ioid > -1) {
					hoverIO(ioid);
				} else {
					hoverCell(moc);
				}
			}
		}
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void handleMOMoveSelectionMode(float x, float y) {
		// TODO Auto-generated method stub

	}

	/**
	 * Handles events that occur when the user clicks on the map.
	 * @param e the {@link MouseEvent}
	 * @param x the x-coordinate
	 * @param y the y-coordinate
	 */
	@Override
	public void handleMouseClick(final MouseEvent e, final float x,
			final float y) {
		try {
			if (super.contains(x, y)) {
				switch (super.getMode()) {
				case GlobalConsts.MAP_MODE_00_FREE:
					handleMCFreeMode(e, x, y);
					break;
				case GlobalConsts.MAP_MODE_01_ACTION_MENU:
					handleMCActionMenuMode(e, x, y);
					break;
				case GlobalConsts.MAP_MODE_02_MOVE_SELECTION:
					handleMCMoveSelectionMode(e, x, y);
					break;
				case GlobalConsts.MAP_MODE_03_MOVE_ANIMATION:
					break;
				default:
					PooledStringBuilder sb =
							StringBuilderPool.getInstance().getStringBuilder();
					sb.append("Map set to invalid mode: ");
					sb.append(super.getMode());
					Exception ex = new Exception(sb.toString());
					sb.returnToPool();
					JOGLErrorHandler.getInstance().fatalError(ex);
					break;
				}
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * Handles events that occur when the user hovers an item.
	 * @param x the x-coordinate
	 * @param y the y-coordinate
	 */
	@Override
	public void handleMouseOver(final float x, final float y) {
		// System.out.println("handleMouseOver::" + super.getMode());
		try {
			if (super.contains(x, y)) {
				switch (super.getMode()) {
				case GlobalConsts.MAP_MODE_00_FREE:
					handleMOFreeMode(x, y);
					break;
				case GlobalConsts.MAP_MODE_01_ACTION_MENU:
					handleMOActionMenuMode(x, y);
					break;
				case GlobalConsts.MAP_MODE_02_MOVE_SELECTION:
					handleMOMoveSelectionMode(x, y);
					break;
				case GlobalConsts.MAP_MODE_03_MOVE_ANIMATION:
					break;
				default:
					PooledStringBuilder sb =
							StringBuilderPool.getInstance().getStringBuilder();
					sb.append("HQMap set to invalid mode: ");
					sb.append(super.getMode());
					Exception ex = new Exception(sb.toString());
					sb.returnToPool();
					JOGLErrorHandler.getInstance().fatalError(ex);
					break;
				}
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean hasCellAt(final float x, final float y) throws Exception {
		return currentMap.getCell((int) x, (int) y) != null;
	}
	/**
	 * Handles actions when the user is interacting with an IO.
	 * @param moc the integer representing the cell the user is hovering
	 */
	private void hoverCell(final int moc) {
		try {
			// clear player mouse highlight
			clearHighlight();
			// see if the mouse is over a cell you can travel to
			lastPathEnd = -1;
			boolean hoverTravelPoint = false;
			for (int outer = 0; outer < travelLocs.length; outer++) {
				if (travelLocs[outer] == moc) {
					// hovering a possible travel point
					// see if this location has an IO
					boolean skip = false;
					for (int inner = 0; inner < ioLocs.length; inner++) {
						if (ioLocs[inner] == moc) {
							HQInteractiveObject io =
									(HQInteractiveObject)
									Interactive.getInstance().getIO(
											ios[inner]);
							// hovering another pc.
							// cannot move to that location
							if (io.hasIOFlag(HQGlobals.IO_01_PC)) {
								skip = true;
								break;
							}
							io = null;
							if (skip) {
								break;
							}
						}
					}
					if (skip) {
						continue;
					}
					PooledStringBuilder sb =
							StringBuilderPool.getInstance()
									.getStringBuilder();
					sb.append("Move to ");
					Point pt =
							UtilityMethods.getInstance().convertIntToPoint(
									moc);
					sb.append(pt.x);
					sb.append(",");
					sb.append(pt.y);
					sb.append(".");
					DungeonScreenView.getInstance().updateActionMessage(
							sb.toString());
					pt = null;
					sb.returnToPool();
					lastPathEnd = moc;
					GameDrawable.setCursor(GameDrawable.MOVE_CURSOR);
					hoverTravelPoint = true;
					break;
				}
			}
			if (!hoverTravelPoint) {
				// player not hovering a travel point. are they hovering a door?
				int ioid =
						HQScript.getInstance().getGlobalIntVariableValue(
								"ioTurn");
				if (currentMap.isCellDoor(moc)
						&& currentMap.cellsAdjacent(
								moc, ioLocs[getIOIndex(ioid)], false)) {
					GameDrawable.setCursor(Cursor.HAND_CURSOR);
					//
					DungeonScreenView.getInstance().updateActionMessage(
							"Open Door.");
				}
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * Handles actions when the user is interacting with an IO.
	 * @param ioid the IO's reference id
	 */
	private void hoverIO(final int ioid) {
		try {
			HQInteractiveObject io =
					(HQInteractiveObject) Interactive.getInstance()
							.getIO(ioid);
			if (io.hasIOFlag(HQGlobals.IO_01_PC)) {
				if (io.getRefId() == HQController.getInstance().getPcId()) {
					// HOVERING THE PLAYER
					playHighlightAnimation(ioid);
				} else {
					// HOVERING A NON-PLAYER HERO
					// STOP PARTY HOVER ANIMATION
					clearHighlight();
				}
			} else if (io.hasIOFlag(HQGlobals.IO_03_NPC)) {
				// HOVERING AN NPC
				clearHighlight();
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected int isMouseOverIO(final float x, final float y) {
		int id = -1;
		for (int i = 0; i < numIosInPlay; i++) {
			// check the IOs location
			Point pt =
					UtilityMethods.getInstance().convertIntToPoint(ioLocs[i]);
			// does the sprites bounding box contain the mouse point?
			Vector3 v = getCellPosition(pt.x, pt.y, HQMap.Z_LEVEL_IOS);
			double leftx = v.getX();
			double bottomy = v.getY();
			if (x >= leftx
					&& x <= leftx + spritesIOs[i].getFullWidth()
					&& y >= bottomy
					&& y <= bottomy + spritesIOs[i].getFullHeight()) {
				// set the id of the IO being hovered
				id = ios[i];
				break;
			}
		}
		return id;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected boolean isMouseOverIO(final int refid, final float x,
			final float y) {
		boolean over = false;
		for (int i = 0; i < ios.length; i++) {
			if (ios[i] == refid) {
				// found the io being looked for
				// check the IOs location
				Point pt = UtilityMethods.getInstance().convertIntToPoint(
						ioLocs[i]);
				// does the sprites bounding box contain the mouse point?
				Vector3 v = getCellPosition(pt.x, pt.y, HQMap.Z_LEVEL_IOS);
				double leftx = v.getX();
				double bottomy = v.getY();
				if (x >= leftx
						&& x <= leftx + spritesIOs[i].getFullWidth()
						&& y >= bottomy
						&& y <= bottomy + spritesIOs[i].getFullHeight()) {
					// set the id of the IO being hovered
					over = true;
				}
				break;
			}
		}
		return over;
	}
	/**
	 * Loads a map.
	 * @param mapName the name of the map file
	 * @throws Exception if an error occurs
	 */
	public void loadMap(final String mapName) throws Exception {
		PooledStringBuilder path =
				StringBuilderPool.getInstance().getStringBuilder();
		path.append(ProjectConstants.getInstance().getMapFilePath());
		if (ProjectConstants.getInstance().getMapFilePath().startsWith("com")
				&& !ProjectConstants.getInstance().getMapFilePath()
						.endsWith("/")) {
			path.append("/");
		} else if (!ProjectConstants.getInstance().getMapFilePath()
				.startsWith("com")
				&& !ProjectConstants.getInstance().getMapFilePath()
						.endsWith("\\")) {
			path.append("\\");
		}
		path.append(mapName);
		InputStream is = getClass().getClassLoader().getResourceAsStream(
				path.toString());
		Document serviceDoc = XMLParserUtility.getInstance().parseXmlFile(is);
		parseMap(serviceDoc);
		path.returnToPool();
		int maxX = 0, maxY = 0;
		for (int i = 0; i < currentMap.getCells().length; i++) {
			maxX = Math.max(maxX, currentMap.getCells()[i].getX());
			maxY = Math.max(maxY, currentMap.getCells()[i].getY());
		}
		super.setMapWidth((maxX + 1) * getTileMapCellSize());
		super.setMapHeight((maxY + 1) * getTileMapCellSize());
		setGraph();
		// set the map mode to allow panning and the action menu
		super.assignFlag(GlobalConsts.MAP_FLAG_ALLOW_ACTION_MENU);
	}
	public void moveIOToLocation(final int ioid, final int location)
			throws Exception {
		ioLocs[getIOIndex(ioid)] = location;
		// decrease the number of moves left
		HQInteractiveObject io =
				(HQInteractiveObject) Interactive.getInstance().getIO(ioid);
		if (io.hasIOFlag(HQGlobals.IO_01_PC)) {
			int m = io.getScript().getLocalVariable("movesLeft").getIntVal();
			m--;
			io.getScript().setLocalVariable("movesLeft", new Integer(m));
		} else if (io.hasIOFlag(HQGlobals.IO_03_NPC)) {
			// to do
		}
		// check for traps
		// check to see if this opens a new room
		// get all rooms the cell is in
		Point pt = UtilityMethods.getInstance().convertIntToPoint(location);
		int[] roomsForCell = currentMap.getRoomsForCell(pt.x, pt.y);
		// set array of flags to indicate if IO has visited new room
		boolean[] roomsFound = new boolean[roomsForCell.length];
		for (int i = 0; i < roomsFound.length; i++) {
			roomsFound[i] = false;
		}
		// get all rooms IO has been in
		HQController.getInstance().getPCObject();
		PooledStringBuilder sb =
				StringBuilderPool.getInstance()
						.getStringBuilder();
		sb.append(HQScript.getInstance()
				.getGlobalStringVariableValue(
						"currentMapName"));
		sb.append("_roomsVisited");
		int[] roomsVisited = io.getScript().getLocalVariable(
				sb.toString()).getIntArrayVal();
		// mark all rooms the IO has already been in
		boolean update = false;
		for (int outer = 0; outer < roomsVisited.length; outer++) {
			for (int inner = 0; inner < roomsForCell.length; inner++) {
				if (roomsVisited[outer] == roomsForCell[inner]) {
					update = true;
					roomsFound[inner] = true;
					break;
				}
			}
		}
		// reset rooms IO has visited
		if (update) {
			for (int i = 0; i < roomsFound.length; i++) {
				if (!roomsFound[i]) {
					// add this room to PC's list
					int[] dest = new int[roomsVisited.length + 1];
					System.arraycopy(
							roomsVisited, 0, dest, 0, roomsVisited.length);
					dest[roomsVisited.length] = roomsForCell[i];
					roomsVisited = dest;
					dest = null;
				}
			}
			io.getScript().setLocalVariable(sb.toString(), roomsVisited);
		}
		sb.returnToPool();
		pt = null;
		roomsForCell = null;
		roomsFound = null;
		io = null;
		roomsVisited = null;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void pan() {
		if (super.getPanning() != GlobalConsts.DIR_NONE) {
			if (hasPanningShiftTimerStarted()) {
				if (readyForPanShift(Time.getInstance().getGameTime())) {
					switch (super.getPanning()) {
					case GlobalConsts.DIR_00_NORTH:
						adjustViewY(GlobalConsts.PAN_SHIFT_PX);
						break;
					case GlobalConsts.DIR_01_NORTH_EAST:
						adjustViewX(GlobalConsts.PAN_SHIFT_PX);
						adjustViewY(GlobalConsts.PAN_SHIFT_PX);
						break;
					case GlobalConsts.DIR_02_EAST:
						adjustViewX(GlobalConsts.PAN_SHIFT_PX);
						break;
					case GlobalConsts.DIR_03_SOUTH_EAST:
						adjustViewX(GlobalConsts.PAN_SHIFT_PX);
						adjustViewY(-GlobalConsts.PAN_SHIFT_PX);
						break;
					case GlobalConsts.DIR_04_SOUTH:
						adjustViewY(-GlobalConsts.PAN_SHIFT_PX);
						break;
					case GlobalConsts.DIR_05_SOUTH_WEST:
						adjustViewX(-GlobalConsts.PAN_SHIFT_PX);
						adjustViewY(-GlobalConsts.PAN_SHIFT_PX);
						break;
					case GlobalConsts.DIR_06_WEST:
						adjustViewX(-GlobalConsts.PAN_SHIFT_PX);
						break;
					case GlobalConsts.DIR_07_NORTH_WEST:
						adjustViewX(-GlobalConsts.PAN_SHIFT_PX);
						adjustViewY(GlobalConsts.PAN_SHIFT_PX);
						break;
					default:
						JOGLErrorHandler.getInstance().fatalError(
								new Exception(
										"Invalid direction: "
												+ super.getPanning()));
						break;
					}
					if (super.getViewX() < 0) {
						super.setViewX(0);
					}
					if (super.getViewX() > super.getMapWidth()
							- HQGlobals.MAP_WIDTH * getTileMapCellSize()) {
						super.setViewX(super.getMapWidth()
								- HQGlobals.MAP_WIDTH * getTileMapCellSize());
					}
					if (super.getViewY() < 0) {
						super.setViewY(0);
					}
					if (super.getViewY() > super.getMapHeight()
							- HQGlobals.MAP_HEIGHT * getTileMapCellSize()) {
						super.setViewY(super.getMapHeight()
								- HQGlobals.MAP_HEIGHT * getTileMapCellSize());
					}
					setPanningShiftTimer(Time.getInstance().getGameTime());
				}
				super.setPanning(GlobalConsts.DIR_NONE);
			} else {
				setPanningShiftTimer(Time.getInstance().getGameTime());
			}
		}
	}
	/**
	 * Pans the "camera" to a specific IO's location.
	 * @param ioid the IO's reference id
	 * @return true if the "camera" has reached the location; otherwise false
	 */
	public boolean panToIO(final int ioid) {
		int ioLoc = ioLocs[getIOIndex(ioid)];
		return panToLocation(this.getCellCenterPoint(ioLoc));
	}
	/**
	 * Pans the "camera" to a specific location.
	 * @param v the location
	 * @return true if the "camera" has reached the location; otherwise false
	 */
	private boolean panToLocation(final Vector2 v) {
		boolean panningDone = false;
		int viewX = super.getViewX();
		int viewY = super.getViewY();
		int panX = (int) v.getX() - getViewportWidth() / 2;
		int panY = (int) v.getY() - getViewportHeight() / 2;
		if (panX < 0) {
			panX = 0;
		} else if (panX > super.getMapWidth()
				- HQGlobals.MAP_WIDTH * getTileMapCellSize()) {
			panX = super.getMapWidth()
					- HQGlobals.MAP_WIDTH * getTileMapCellSize();
		}
		if (panY < 0) {
			panY = 0;
		} else if (panY > super.getMapHeight()
				- HQGlobals.MAP_HEIGHT * getTileMapCellSize()) {
			panY = super.getMapHeight()
					- HQGlobals.MAP_HEIGHT * getTileMapCellSize();
		}
		if (viewX == panX) {
			// reached x center
			if (viewY == panY) {
				// reached the end. no more panning
				super.setPanning(GlobalConsts.DIR_NONE);
				panningDone = true;
			} else {
				// not reached y center. figure out which way to move
				if (viewY < panY) {
					// pan north
					super.setPanning(GlobalConsts.DIR_00_NORTH);
				} else {
					// pan south
					super.setPanning(GlobalConsts.DIR_04_SOUTH);
				}
			}
		} else {
			// not reached x center yet
			if (viewY == panY) {
				// reached y center. figure out which way to move
				if (viewX < panX) {
					// pan east
					super.setPanning(GlobalConsts.DIR_02_EAST);
				} else {
					// pan west
					super.setPanning(GlobalConsts.DIR_06_WEST);
				}
			} else {
				// not reached y center. figure out which way to move
				if (viewY < panY) {
					if (viewX < panX) {
						// pan northeast
						super.setPanning(GlobalConsts.DIR_01_NORTH_EAST);
					} else {
						// pan northwest
						super.setPanning(GlobalConsts.DIR_07_NORTH_WEST);
					}
				} else {
					if (viewX < panX) {
						// pan southeast
						super.setPanning(GlobalConsts.DIR_03_SOUTH_EAST);
					} else {
						// pan southwest
						super.setPanning(GlobalConsts.DIR_05_SOUTH_WEST);
					}
				}
			}
		}
		return panningDone;
	}
	/**
	 * Parses an xml document.
	 * @param dom the xml document
	 */
	private void parseMap(final Document dom) throws Exception {
		currentMap = new UltimaTileMap();
		Element docEle = dom.getDocumentElement();
		// get a nodelist of mapHex elements
		NodeList nl = docEle.getElementsByTagName("cell");
		if (nl != null && nl.getLength() > 0) {
			for (int i = 0; i < nl.getLength(); i++) {
				// create the cell
				Element element = (Element) nl.item(i);
				UltimaTileCell cell = createCell(element);
				if (cell != null) {
					currentMap.addCell(cell);
				}
			}
		}
		currentMap.clearRooms();
		nl = docEle.getElementsByTagName("room");
		if (nl != null && nl.getLength() > 0) {
			for (int i = 0; i < nl.getLength(); i++) {
				ArrayList<UltimaTileCell> list =
							new ArrayList<UltimaTileCell>();
				Element element = (Element) nl.item(i);
				NodeList nl2 = element.getElementsByTagName("roomcell");
				if (nl2 != null && nl2.getLength() > 0) {
					for (int j = 0; j < nl2.getLength(); j++) {
						// create the cell
						Element element2 = (Element) nl2.item(j);
						int x = XMLParserUtility.getInstance().getIntValue(
								element2, "x");
						int y = XMLParserUtility.getInstance().getIntValue(
								element2, "y");
						list.add(currentMap.getCell(x, y));
					}
				}
				currentMap.addRoom(
						list.toArray(new UltimaTileCell[list.size()]));
			}
		}
		// add open door terrain
		HQTerrainTypes.getInstance().addTerrain("ultima_door_open_to_floor");
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void playHighlightAnimation(final int refId) {
		setHighlightPosition(refId);
		GameDrawable.setCursor(Cursor.HAND_CURSOR);
		super.assignFlag(GlobalConsts.MAP_FLAG_PARTY_HOVER);
		try {
			Animation.getInstance().playAnimation(
					highlight,
					highlight.getAnimation("selection"));
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void prepareForRendering() throws Exception {
		try {
			if (super.isVisible()) {
				clearMovementPath();
				handleAnimation();
				pan();
				renderCells();
				renderIOs();
				renderMovePath();
				if (highlight.getSprite().getPosition() != null) {
					Render.getInstance().registerEntity(
							highlight.getSprite());
				}
				System.out.print("PC is in rooms ");
				for (int i = 0; i < this.getRoomsForIO(HQController.getInstance().getPcId()).length; i++) {
					System.out.print(getRoomsForIO(HQController.getInstance().getPcId())[i]);
					System.out.print(" ");
				}
				System.out.println("");
				super.prepareForRendering();
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * After a left-click on the player's sprite during FREE MAP MODE, prepare
	 * to show the action menu by centering on the player and showing hover
	 * icon.
	 * @param refId the player's reference id
	 */
	private void prepareToShowActionMenu(final int refId) {
	}
	public void removeIOFromMap(HQInteractiveObject io) {
		if (io != null) {
			// check to see if io is already tracked
			boolean inList = false;
			int index = 0;
			for (; index < ios.length; index++) {
				if (Interactive.getInstance().hasIO(ios[index])) {
					inList = true;
					break;
				}
			}
			if (inList) {
				if (index == 0) {
					// move everything back one position in ios array
					int[] dest = new int[ios.length];
					System.arraycopy(ios, 1, dest, 0, ios.length - 1);
					dest[ios.length - 1] = -1;
					ios = dest;
					// move everything back one position in ioLocs array
					dest = new int[ioLocs.length];
					System.arraycopy(ioLocs, 1, dest, 0, ioLocs.length - 1);
					ioLocs = dest;
					dest = null;
				} else if (index == ios.length - 1) {
					// index is last index in array.
					// set last index value to invalid
					ios[index] = -1;
					ioLocs[index] = -1;
				} else {
					// index is somewhere in the middle
					int[] dest = new int[ios.length];
					// copy everything leading up to index
					System.arraycopy(ios, 0, dest, 0, index);
					// copy everything after index
					System.arraycopy(ios, index + 1, dest, index,
							ios.length - 1 - index);
					// last item in array is set to -1
					dest[ios.length - 1] = -1;
					ios = dest;

					dest = new int[ioLocs.length];
					// copy everything leading up to index
					System.arraycopy(ioLocs, 0, dest, 0, index);
					// copy everything after index
					System.arraycopy(ioLocs, index + 1, dest, index,
							ioLocs.length - 1 - index);
					// last item in array is set to -1
					dest[ioLocs.length - 1] = -1;
					ioLocs = dest;
					dest = null;
				}
				numIosInPlay--;
			}
		}
	}
	/**
	 * Renders all map cells.
	 * @throws Exception if an error occurs
	 */
	private void renderCells() throws Exception {
		int lastTileIndex = 0;
		// see which cells need to be rendered
		// get all cells for rooms the PC has visited
		HQInteractiveObject pc =
				(HQInteractiveObject) Interactive.getInstance().getIO(
						HQController.getInstance().getPcId());
		// get all rooms the PC has visited
		PooledStringBuilder sb =
				StringBuilderPool.getInstance().getStringBuilder();
		sb.append(HQScript.getInstance().getGlobalStringVariableValue(
				"currentMapName"));
		sb.append("_roomsVisited");
		int[] visited =
				pc.getScript().getLocalVariable(sb.toString()).getIntArrayVal();
		sb.returnToPool();
		// show cells only for rooms the PC has visited
		for (int outer = 0; outer < visited.length; outer++) {
			UltimaTileCell[] cells = currentMap.getRoom(visited[outer]);
			for (int i = 0; i < cells.length; i++) {
				Vector3 v = getCellPosition(
						cells[i].getX(), cells[i].getY(), HQMap.Z_LEVEL_TILES);
				boolean show = UtilityMethods.getInstance().intersects(
						(int) getViewPortX(),
						(int) getViewPortY(),
						getViewportWidth(),
						getViewportHeight(),
						(int) v.getX(),
						(int) v.getY(),
						getTileMapCellSize(),
						getTileMapCellSize()
						);
				if (show) {
					spritesTiles[lastTileIndex].setPosition(v);
					spritesTiles[lastTileIndex].setImageRefId(cells[i]
							.getImageRefId());
					spritesTiles[lastTileIndex].setWidth(getTileMapCellSize());
					spritesTiles[lastTileIndex].setHeight(getTileMapCellSize());

					Sprite.getInstance().setPartiallyRendered(
							spritesTiles[lastTileIndex],
							v.getY() + spritesTiles[lastTileIndex].getHeight(),
							v.getX(),
							v.getY(),
							v.getX() + spritesTiles[lastTileIndex].getWidth(),
							getViewPortY() + getViewportHeight(),
							getViewPortX(),
							getViewPortY(),
							getViewPortX() + getViewportWidth());
					Render.getInstance()
							.registerEntity(spritesTiles[lastTileIndex]);
					lastTileIndex++;
				}
			}
		}
	}
	/**
	 * Renders all Interactive Objects.
	 * @throws Exception if an error occurs
	 */
	private void renderIOs() throws Exception {
		// find the pc's ref index in the list of ios
		int pcIndex = -1;
		for (int i = 0; i < ios.length; i++) {
			if (ios[i] == HQController.getInstance().getPcId()) {
				pcIndex = i;
				break;
			}
		}
		for (int i = 0; i < ios.length; i++) {
			if (Interactive.getInstance().hasIO(ios[i])) {
				HQInteractiveObject io =
						(HQInteractiveObject) Interactive.getInstance().getIO(
								ios[i]);
				Point iopt =
						UtilityMethods.getInstance().convertIntToPoint(
								ioLocs[i]);
				Vector3 v =
						HQMovement.getInstance().getIOVector(ios[i],
								HQMap.Z_LEVEL_IOS);
				// the PC must be rendered when possible
				if (io.hasIOFlag(HQGlobals.IO_01_PC)
						&& io.getRefId() != ios[pcIndex]) {
					// a PC, but not the player. are they in the same room?
					Point pcpt = UtilityMethods.getInstance()
							.convertIntToPoint(
									ioLocs[pcIndex]);
					int[] pcRooms = currentMap.getRoomsForCell(pcpt.x, pcpt.y);
					int[] ioRooms = currentMap.getRoomsForCell(iopt.x, iopt.y);
					boolean sameRoom = false;
					for (int outer = 0; outer < pcRooms.length; outer++) {
						for (int inner = 0; inner < ioRooms.length; inner++) {
							if (pcRooms[outer] == ioRooms[inner]) {
								sameRoom = true;
								break;
							}
						}
					}
					pcpt = null;
					pcRooms = null;
					ioRooms = null;
					if (!sameRoom) {
						iopt = null;
						v = null;
						continue;
					}
				} else if (io.hasIOFlag(HQGlobals.IO_03_NPC)) {
					// NPC
				}
				boolean show = UtilityMethods.getInstance().intersects(
						(int) getViewPortX(),
						(int) getViewPortY(),
						getViewportWidth(),
						getViewportHeight(),
						(int) v.getX(),
						(int) v.getY(),
						getTileMapCellSize(),
						getTileMapCellSize()
						);
				if (show) {
					// show the io
					if (io.hasIOFlag(HQGlobals.IO_01_PC)) {
						// set the sprite image
						spritesIOs[i].setImageRefId(
								io.getPCData().getSpriteRef());
					}
					spritesIOs[i].setPosition(v);
					spritesIOs[i].setWidth(getTileMapCellSize());
					spritesIOs[i].setHeight(getTileMapCellSize());
					Sprite.getInstance().setPartiallyRendered(
							spritesIOs[i],
							v.getY() + spritesIOs[i].getHeight(),
							v.getX(),
							v.getY(),
							v.getX() + spritesIOs[i].getWidth(),
							getViewPortY() + getViewportHeight(),
							getViewPortX(),
							getViewPortY(),
							getViewPortX() + getViewportWidth());
					Render.getInstance().registerEntity(spritesIOs[i]);
				}
				iopt = null;
				io = null;
				v = null;
			}
		}
	}
	/**
	 * Renders all path highlights when the player is choosing a move.
	 * @throws Exception if an error occurs
	 */
	private void renderMovePath() throws Exception {
		if (lastPathEnd > -1) {
			GameDrawable.setCursor(GameDrawable.MOVE_CURSOR);
			// mouse is over travel path
			// prepare highlight
			Point pt = UtilityMethods.getInstance().convertIntToPoint(
					lastPathEnd);
			int[] path = HQDijkstraSearch.getInstance().getPathToTarget(pt);
			// the index of the last path sprite that was assigned a
			// position
			int lastPathindex = 0;
			for (int j = 0; j < path.length; j++) {
				Point pt2 =
							UtilityMethods.getInstance().convertIntToPoint(
									path[j]);
				Vector3 v = getCellPosition(
							pt2.x, pt2.y, HQMap.Z_LEVEL_TILE_HIGHLIGHTS);
				boolean show = UtilityMethods.getInstance().intersects(
							(int) getViewPortX(),
							(int) getViewPortY(),
							getViewportWidth(),
							getViewportHeight(),
							(int) v.getX(),
							(int) v.getY(),
							getTileMapCellSize(),
							getTileMapCellSize()
							);
				if (show) {
					spritesMovePath[lastPathindex].setPosition(v);
					Sprite.getInstance().setPartiallyRendered(
								spritesMovePath[lastPathindex],
								v.getY()
										+ spritesMovePath[lastPathindex]
												.getHeight(),
								v.getX(),
								v.getY(),
								v.getX()
										+ spritesMovePath[lastPathindex]
												.getWidth(),
								getViewPortY() + getViewportHeight(),
								getViewPortX(),
								getViewPortY(),
								getViewPortX() + getViewportWidth());
					Render.getInstance().registerEntity(
								spritesMovePath[lastPathindex]);
					lastPathindex++;
				}
			}
		}
	}
	/** Resets all possible travel locations for the current pc. */
	public void resetTravelLocations() {
		travelLocs = null;
		setMoveHighlights();
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setBorder(final GuiBorder newBorder) {
		size = null;
		super.setBorder(newBorder);
	}
	/**
	 * Sets the mode for the animation that should be displayed on the map.
	 * @param val the animation mode.
	 */
	public void setCurrentAnimation(final int val) {
		currAnimation = val;
	}
	/**
	 * Uncovers all secret doors in the same room as an IO, changing them from
	 * secret doors to closed doors.
	 * @param ioid the io's reference id
	 * @param endTurnAfterSearch flag indicating that the PC's turn should end
	 * after performing the search.
	 * @throws Exception if an error occurs
	 */
	public void findSecretDoorsInRoom(final int ioid, 
			final boolean endTurnAfterSearch) throws Exception {
		System.out.println("findSecretDoorsInRoom(" + ioid);
		int[] rooms = this.getRoomsForIO(ioid);
		int[] doors = new int[0];
		int secret = HQTerrainTypes.getInstance().getTerrainId(
				"ultima_secret_door_closed_to_floor");
		for (int outer = 0; outer < rooms.length; outer++) {
			UltimaTileCell[] cells = this.getRoom(rooms[outer]);
			for (int inner = 0; inner < cells.length; inner++) {
				if (cells[inner].getType() == secret) {
					int loc = 
						UtilityMethods.getInstance().convertPointToInt(
								cells[inner].getX(), cells[inner].getY());
					// found one. add to list
					int[] dest = 
						new int[doors.length + 1];
					System.arraycopy(doors, 0, dest, 0, doors.length);
					dest[doors.length] = loc;
					doors = dest;
					dest = null;
				}
			}
		}

		PooledStringBuilder sb =
				StringBuilderPool.getInstance()
						.getStringBuilder();
		sb.append(HQScript.getInstance()
				.getGlobalStringVariableValue(
						"currentMapName"));
		sb.append("_roomsDoorSearched");
		int[] visited = HQScript.getInstance().getGlobalIntArrayVariableValue(
				sb.toString());
		HQScript.getInstance().setGlobalVariable(
				sb.toString(), 
				UtilityMethods.getInstance().extendIntArray(
						visited, getRoomsForIO(ioid)));
		sb.returnToPool();
		if (doors.length > 0) {
		HQMovement.getInstance().revealSecretDoors(
				doors, getRoomsForIO(ioid)[0], endTurnAfterSearch);
		} else {
			Interface.getInstance().showSmallMessageWindow(
					"Search", 
					"No secret doors were found.",
					new Action() {
						/**
						 * {@inheritDoc}
						 */
						@Override
						public void performAction() {
							try {
								Interface.getInstance().hideSmallMessageWindow();
								if (endTurnAfterSearch) {
									HQTurn.getInstance().nextIOTurnCycle();
								}
							} catch (Exception ex) {
								JOGLErrorHandler.getInstance().fatalError(ex);
							}
						}
					});
		}
	}
	public int getTerrainIOIsOn(final int ioid) {
		Point pt = UtilityMethods.getInstance().convertIntToPoint(
				ioLocs[getIOIndex(ioid)]);
		int terrain = getCellType(pt.x, pt.y);
		pt = null;
		return terrain;
	}
	/**
	 * Sets the graph for the map.
	 * @throws Exception if an error occurs
	 */
	private void setGraph() throws Exception {
		// add all map cells to graph as nodes
		HQCellGraph.getInstance().clear();
		for (int i = 0; i < currentMap.getCells().length; i++) {
			HQCellGraph.getInstance().addNode(currentMap.getCells()[i].getX(),
					currentMap.getCells()[i].getY());
		}
		// add all edges
		for (int i = 0; i < currentMap.getCells().length; i++) {
			int x = currentMap.getCells()[i].getX();
			int y = currentMap.getCells()[i].getY();
			int from = HQCellGraph.getInstance().getNode(x, y).getIndex();
			int floor = HQTerrainTypes.getInstance().getTerrainId(
					"ultima_floor_to_wall");
			int open = HQTerrainTypes.getInstance().getTerrainId(
					"ultima_door_open_to_floor");
			// check north - is it a floor or open door?
			if (currentMap.getCell(x, y + 1) != null) {
				int to = HQCellGraph.getInstance().getNode(x, y + 1).getIndex();
				// add an edge between cells
				WeightedGraphEdge edge = new WeightedGraphEdge(from, to, 1);
				HQCellGraph.getInstance().addEdge(edge);
			}
			// check east - is it a floor or open door?
			if (currentMap.getCell(x + 1, y) != null) {
				int to = HQCellGraph.getInstance().getNode(x + 1, y).getIndex();
				// add an edge between cells
				WeightedGraphEdge edge = new WeightedGraphEdge(from, to, 1);
				HQCellGraph.getInstance().addEdge(edge);
			}
			// check south - is it a floor or open door?
			if (currentMap.getCell(x, y - 1) != null) {
				int to = HQCellGraph.getInstance().getNode(x, y - 1).getIndex();
				// add an edge between cells
				WeightedGraphEdge edge = new WeightedGraphEdge(from, to, 1);
				HQCellGraph.getInstance().addEdge(edge);
			}
			// check west - is it a floor or open door?
			if (currentMap.getCell(x - 1, y) != null) {
				int to = HQCellGraph.getInstance().getNode(x - 1, y).getIndex();
				// add an edge between cells
				WeightedGraphEdge edge = new WeightedGraphEdge(from, to, 1);
				HQCellGraph.getInstance().addEdge(edge);
			}
		}
	}
	/**
	 * Sets the highlight position.
	 * @param refId the reference id of the IO being highlighted
	 */
	private void setHighlightPosition(final int refId) {
		int index = 0;
		for (; index < numIosInPlay; index++) {
			if (ios[index] == refId) {
				break;
			}
		}
		Point pt = UtilityMethods.getInstance().convertIntToPoint(
				ioLocs[index]);
		double ax = pt.getX();
		ax -= super.getViewX();
		ax += super.getPosition().getX();
		double ay = pt.getY();
		ay -= super.getViewY();
		ay += super.getPosition().getY();
		double az = 0;
		if (super.getPosition() != null) {
			az = super.getPosition().getZ();
		}
		az += GlobalConsts.CHILD_STEP * GlobalConsts.INT_003;
		highlight.getSprite().setPosition(
				getCellPosition(pt.x, pt.y, HQMap.Z_LEVEL_IO_HIGHLIGHTS));
		pt = null;
	}
	/**
	 * Sets the tile type for a specific cell index.
	 * @param cellIndex the cell's index
	 * @param tileType the new tile type
	 * @throws Exception if an error occurs
	 */
	public void setMapCellType(final int cellIndex, final int tileType)
			throws Exception {
		currentMap.getCell(cellIndex).setType(tileType);
	}
	/** Sets the location of the movement highlights for the player. */
	private void setMoveHighlights() {
		try {
			if (travelLocs == null) {
				travelLocs = new int[0];
				HQDijkstraSearch.getInstance().setGraph(
						HQCellGraph.getInstance());
				int ioid =
						HQScript.getInstance().getGlobalIntVariableValue(
								"ioTurn");
				HQDijkstraSearch.getInstance().searchByIO(ioid);
				int len = HQDijkstraSearch.getInstance().getNumPredecessors();
				for (int i = 0; i < len; i++) {
					if (HQDijkstraSearch.getInstance().getPredecessor(i) > -1) {
						Point pt = HQCellGraph.getInstance().getNode(
								i).getLocation();
						int[] dest = new int[travelLocs.length + 1];
						System.arraycopy(travelLocs, 0, dest, 0,
								travelLocs.length);
						dest[travelLocs.length] =
								UtilityMethods.getInstance().convertPointToInt(
										pt);
						travelLocs = dest;
						dest = null;
					}
				}
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setPreferredSize(final int width, final int height) {
		// TODO Auto-generated method stub

	}
}
